閱讀本篇文章前,仔細想想看
- 什麼是宣告檔 Declaration Files?為何宣告檔很重要?
- 如何載入第三方套件在 TypeScript 專案裡?
- 如何執行 TypeScript 在 AMD 模式下的編譯成果?
如果還沒理解完畢的話,可以先翻看前一篇文章喔!
今天筆者又要講讀者應該會更常用的部分 —— 使用 Webpack 結合 TypeScript。
讀者可能覺得:“作者真是莫名其妙,為何一開始就不講這個?還要花很多篇時間講 Namespaces 或單純打包檔案等等東西。”
筆者之所以這麼做的原因是,要先讓讀者打好一些基礎 —— 像是宣告檔 Declaration Files 的目的與由來、自己如何建立簡單的 TypeScript 環境等等,畢竟在進行簡單的語法測試或學習時,你不太需要那麼大型的環境。
另外,筆者直接進入 Webpack 單元講也是可以,只不過讀者提前知道宣告檔的存在 —— 並且有能力善用工具 —— 可以獨立查詢套件的型別宣告規格的話,要開始跟一些專案應用層面的內容會比想像中簡單喔!
本篇文章就進入正文開始吧~
這應該是大部分讀者想要知道的東西,筆者也總算可以從前幾篇很痛苦的設定來設定去的泥淖中,準備從這一篇解脫。
筆者稍微簡介一下 Webpack,避免有些讀者可能還是對 Webpack 這東西有些疑惑。(圖一為官方網站對於 Webpack 的示意圖)
圖一:解釋 Webpack 最簡單的架構圖
可以從圖一得知,事實上 Webpack 不僅僅只有打包純 JavaScript 相關的檔案而已,它連任何靜態資源,諸如:CSS、Sass、Image 等等相關的檔案都可以打包在一起。
而打包專案一切的方式就是用所謂的 Loader 進行,比如說:想要將 CSS 檔案打包進去,就會有所謂的 css-loader
;想要編譯 Sass 相關的檔案,就會有 sass-loader
之類的東西 —— 而如果今天要將 TypeScript 的檔案編譯並且打包進去,官方就有出所謂的 ts-loader
。
因此,筆者今天就先示範如何建構單純的 Webpack 結合 TypeScript 的環境吧!當然,讀者可以點這裡檢視 Webpack 官方網站以及參考如何將 TypeScript 結合到 Webpack 的相關設定喔!
首先,在任何想要建構環境的資料夾中下一系列的指令:
// 到你想要建構環境的資料夾內
$ cd PATH_TO_DIR
// 初始化 package.json
$ npm init -y
// 初始化 tsconfig.json
$ tsc --init
首先,筆者下載兩個跟 webpack
相關的套件:
$ npm install webpack webpack-cli --save-dev
另外,因為我們需要讓 TypeScript 與專案結合,必須要重新下載 typescript
與下載打包時需要的 Loader —— 也就是 ts-loader
:
$ npm install typescript ts-loader --save-dev
讀者可能覺得:“為何要重新下載 typescript
模組?這東西不是早在本系列第一天就已經下載過了,而且是用 npm
模組的 global
模式下載的?”
事實上 Webpack 認得的 TypeScript 編譯器並不會從 Global 的 NPM 模組參照,而是在專案內部參照。因此每一次建構 Webpack 結合 TypeScript 的環境時,必須額外下載 typescript
在專案內部。
習慣上,開發時我們會將主程式都放在名為 /src
的資料夾;打包專案時則會將結果輸出到 /build
或 /dist
的資料夾(又或者是看讀者有什麼習慣)。
以下分別新增兩種專案資料夾,並且額外新增 index.ts
到 /src
資料夾裡,代表主程式撰寫的地方喔~
// 建構 /src 與 /dist 這兩種不同的檔案資料夾
$ mkdir src
$ mkdir dist
// 新增 ./src/index.ts 檔案
$ touch ./src/index.ts
在 index.ts
裡隨便寫一行 console.log
:
另外,我們也需要設定 TypeScript 編譯器的檔案:
{
"compilerOptions": {
/* 略... */
"target": "es5",
"module": "es6",
"outDir": "./dist/",
"rootDir": "./src/",
"strict": "true",
"noImplicitAny": true,
"strictNullChecks": true
/* 略... */
}
}
以上是筆者設定過的東西,其他如果有一開始預設的設定被啟動可以放著。
另外,建立 webpack.config.js
並且填入以下內容:
以下筆者稍微解釋 webpack
的設定檔到底寫了些什麼:
entry
部分代表的是 Webpack 要打包的檔案輸入位置,也就是 ./src/index.ts
檔module
裡面通常都是放置 Loader 相關設定 —— 其中 TypeScript 相關檔案都會經由 ts-loader
進行編譯處理的動作output
則是設定 Webpack 打包過後的專案輸出點 —— 以上面的設定來說,它會把檔案打包到 ./dist
資料夾內,並且取名為 bundle.js
接之前筆者有提過我們可以使用 lite-server
簡單地 Host HTML 檔案並且監控靜態檔案的變更 —— 如果該 HTML 檔引入的 JavaScript 檔案有被變動時,就會自動幫我們刷新頁面。
首先,我們當然得先要有 index.html
來測試我們編譯過後的 JavaScript 檔案,以下是 index.html
的程式碼內容。
另外,我們除了想要使用 lite-server
外,通常使用 webpack
打包專案時,可以使用 webpack -w
開啟 Watch 模式 —— 只要專案一有變動,Webpack 就會自動重新打包並更新輸出結果。
不過這樣子又會遇到必須同時開兩個終端機,分別執行 lite-server
與 webpack -w
這兩個指令,於是我們也可以使用筆者之前提到的 concurrently
這個套件 —— 協助我們同時執行這兩種指令。
首先,下載 lite-server
與 concurrenly
這兩個套件:
$ npm install lite-server concurrently --save-dev
並且將 package.json
裡的 scripts
修改成:
{
/* 略... */
"scripts": {
"start:watch": "webpack -w",
"start:serve": "lite-server",
"start": "concurrently npm:start:*"
},
/* 略... */
}
我們就可以使用 npm start
同時執行 webpack -w
與 lite-server
這兩種指令。
以下是下達 npm start
指令時,VSCode 編譯器的結果。(如圖二)
圖二:一連串執行的過程並且成功地將專案打包出來
另外,lite-server
會自動打開瀏覽器,並且鎖定 localhost:3000
這個 Port。(如果本來的 3000 Port 被佔據了,則會打開 3001,依此類推)(如圖三)
圖三:打包後清楚地印出 Hello World!
字串
以上就是完整的 Webpack 結合 TypeScript 基礎的環境開發流程!
本篇唯一重點. 使用 Webpack 結合 TypeScript 建立環境
除了 Webpack 基本的套件與設定外,最主要需要注意的是:
- 必須要下載
typescript
模組到專案裡,因為 Webpack 的ts-loader
必須仰賴專案內部的編譯器,而非全域- TypeScript 檔案對應的 Loader 是
ts-loader
- 通常使用 Webpack 時,就不太管
tsconfig.json
裡面的outFile
這個選項,因為 Webpack 會幫你處理好整個打包流程,你也不需要再為其他模組規範擔心來擔心去的- 記得啟動
tsconfig.json
裡跟語法或型別監測相關的設定 —— 例如noImplicitAny
或strictNullChecks
既然環境都設定完畢了,筆者想順便簡單應用目前所學到的東西,示範小專案如何應用 TypeScript 的各種 Feature:諸如型別、類別、介面等等語法的協作。
本範例筆者想要示範的是使用 LeafletJS 打造台北市的 UBike 即時地圖 —— 使用的自然是台北市開放資料中的 UBike 台北市公共自行車即時資訊。
如果對 LeafletJS 不熟悉的讀者們,可以將它想成類似於 Google Map 的套件(但不是 Google Map XD)。(圖四為 LeafletJS 官方的網站截圖)
圖四:LeafletJS 官方網站
另外,由於本篇內容已經花了一半的篇幅在解說 Webpack + TypeScript 的環境建構流程,因此下半篇只能先把進度推到先把主要地圖建構出來。不過光是這樣的簡單過程,筆者就可以扯到關於套件的型別宣告檔以及一些技巧,這都是在前一篇都討論過的東西,因此這裡會再次示範,不失為一個練習的好機會。
由於官方的 LeafletJS 的套件 leaflet
是用原生 JavaScript 的程式碼實作的,因此我們也必須同時下載 @types/leaflet
。
$ npm install leaflet @types/leaflet
由於 LeafletJS 的地圖也有官方的 CSS 檔案需要載入,這裡筆者選擇用它們的 CDN 載入到 HTML 檔案裡。此外,我們也會需要提供一個標籤負責作為地圖的容器,因此在 HTML 檔案裡筆者選擇使用 <div id="map"></div>
作為地圖的容器。以下是 index.html
目前的結果。
當然,如果對於 LeafletJS 提供的功能有任何問題可以參考官方網站的 Doc。這裡筆者就按照自己寫程式的過程一一記錄下來。
首先,筆者在 index.ts
寫下產生地圖的程式。
不過一開始就碰到問題了,TypeScript 對於 taiwanCoord
變數的使用有些警告。(如圖五)
圖五:很明顯地,setView
方法要求第一個參數的型別為 LatLngExpression
而非 number[]
讀者可能覺得疑惑,明明沒有任何 LatLngExpression
的蹤跡為何會出現這種奇怪的訊息。
其實重點在下一句話:Type 'number[]' is missing the following properties from type '[number, number]': 0, 1
其中,不覺得 [number, number]
這個格式很熟悉嗎?這不就是在很久以前討論過的元組型別嗎?
如果讀者還記得的話,筆者當時有說過:
元組型別一定要進行積極註記,不然會被 TypeScript 自動推論為陣列相關的型別
因此筆者將 index.ts
中的 taiwanCoord
變數宣告的時候註記 LatLngExpression
,不過這時候 VSCode 提供的 Auto-Complete 功能非常好用(如圖六)。
圖六:VSCode 會自動彈出開發者可以註記的型別
其中最重要的一點在後面的提示字:Auto import from 'leaflet'
—— 也就是說,當筆者選擇這項功能的時候,VSCode 會自動將 LatLngExpression
自動幫我們載入進去,這是非常好用的功能!
此時編輯器內部不會出現任何錯誤訊息(如圖七),此外瀏覽器可以顯示出地圖的結果(如圖八)!
圖七:編輯器內部除了沒有錯誤訊息外,Webpack 正在幫我們打包檔案呢!
圖八:地圖出來了~
首先,筆者必須說,因為太多亂七八糟的設定摻雜在一起,包含:
taipeiCoord
代表的是地圖初始的聚焦點zoom
代表預設的地圖縮放等級'map'
字串地圖裝載的容器之標籤的 IDtileLayer
方法裡有一個是代表地圖的背景 URL因此筆者額外建立 map.config.ts
這個檔案在 /src
資料夾裡並填入這些資訊:
並且在 index.ts
裡引入 map.config.ts
(當然,讀者可能也會想要用 JSON 格式,不過後面筆者會說明為何採用 .ts
)。
不過!這裡ㄧ樣會遇到剛剛的問題 —— coordinate
會被推論為 number[]
而非 LatLngExpression
,因此我們可以選擇註記 coordinate
為 LatLngExpression
;又或者,乾脆在 map.config.ts
宣告該設定檔的 JSON 物件格式並積極註記它,以下是 map.config.ts
的結果。
這樣子,原本的 index.ts
檔案裡的 coordinate
就會被自動推論為 LatLngExpression
了!(如圖九)
圖九:將型別註記整理到其他地方,主程式就不會特別繁雜,而且 TypeScript 還會自動幫我們載入進來的變數之型別推論結果。
以上就是筆者選擇用 map.config.ts
而非 .json
格式的原因 —— 主程式檔案不會充滿繁雜的註記,但是仍舊在 TypeScript 型別系統的監控下!
注意,這一句話是重點:
必須要讓 TypeScript 專案掌控在型別系統之下
很多人以為使用 TypeScript 就是一股腦兒地要讓全部變數型別等等都要被註記下去,但事實上 —— 光是善用 TypeScript 的型別推論有很多優點:
這是要善用 TypeScript 的優勢,而不是被工具逼迫一定要每次宣告新的東西就得進行註記。
如果讀者有 Follow 前一篇文章講到的技巧 —— 可以藉由型別推斷出來的結果,使用 VSCode 就可以追溯型別被宣告的原始碼所在位置。
首先,假設我們想要知道 Leaflet Map 的個體到底可以使用什麼功能,我們可以將滑鼠指向 L.map
那個方法。
再來是:
底下出現底線。(如圖十)
圖十:L.map
被顯示出來額外資訊
點下去就會出現很精彩的 —— LeafletJS 套件的型別宣告檔(Declaration File)。(如圖十一)
圖十一:這是 L.map
的型別宣告
這裡讀者就可以不用上網查資料就可以知道 —— 原來 L.map
後面還有 MapOptions
。
重複剛剛的步驟,檢索 MapOptions
。(如圖十二)
圖十二:檢索 MapOptions
的所有功能
哇噻~你不需要上網查,你就知道 MapOptions
裡面可以填入什麼樣的資料,以下筆者列舉幾個:
preferCanvas?
—— 筆者猜測可能是將 Container 繪出地圖的過程改採用 HTML5 Canvas,而非 SVG 模式zoomControl?
—— 如果為 false
代表該地圖不能被縮放dragging?
—— 如果為 false
則該地圖不能被滑鼠拖曳所以筆者要再強調一次:
你如果會使用型別宣告檔查詢功能的話,花在瀏覽器查詢 Doc 的時間會大大減少!
這就是筆者強調 Declaration Files 重要的地方 —— 它們可是套件的規格所在地呢!
筆者今天光是講簡單的地圖建構就花了另外五千字講解 —— 善用工具的技巧是筆者首要推廣的目標。
下一篇我們就緊接著開始開發更多 UBike 地圖相關的功能喔~